package My

import (
	"db2s/gis"
	"db2s/parDef"
	"db2s/ref"
	"encoding/json"
	"fmt"
	"strings"
)

type Feature struct {
	Type       string          `json:"type"` // "Feature"
	Geometry   json.RawMessage `json:"geometry"`
	Properties map[string]any  `json:"properties,omitempty"`
	// 可选：持有 SRid 以便轴序判断（WKT/GeoJSON通常不含SRid，需要外部传入）
	SRid *int `json:"-"`
}
type FeatureCollection struct {
	Type     string    `json:"type"` // "FeatureCollection"
	Features []Feature `json:"features"`
}
type gisValQueryComposition struct {
	gisValue string
	rid      string
	axis     parDef.AxisOrder
	gType    string
}
type Coordinates struct {
	Type        string          `json:"type"`
	Coordinates json.RawMessage `json:"coordinates"`
}

func newSplitGisValue(s string) (res gisValQueryComposition) {
	if !strings.Contains(s, "@STYPE:") {
		return
	}
	qq := strings.Split(s, "@STYPE:")
	res.gisValue = qq[0]
	for k, v := range strings.Split(qq[1], ",") {
		if k == 0 {
			res.gType = v
			continue
		}
		switch {
		case strings.Contains(v, "SRID:"):
			if len(strings.ReplaceAll(v, "SRID:", "")) == 0 {
				res.rid = "0"
			} else {
				res.rid = strings.ReplaceAll(v, "SRID:", "")
			}
		case strings.Contains(v, "AXIS:"):
			var axis string
			if len(strings.ReplaceAll(v, "AXIS:", "")) == 0 {
				axis = "EPSG"
			} else {
				axis = strings.ReplaceAll(v, "AXIS:", "")
			}
			switch axis {
			case "EPSG":
				res.axis = parDef.AxisEPSG
			case "OGC":
				res.axis = parDef.AxisOGC
			}
		}
	}
	return
}

// 依据选择的轴序策略与 SRid 决定是否翻转
func shouldFlip(axis parDef.AxisOrder, SRid string) bool {
	switch axis {
	case parDef.AxisOGC:
		// OGC/GeoJSON：不翻转，保持 [lon,lat]
		return false
	case parDef.AxisEPSG:
		// 你可以更精细：只有特定 SRid 翻转
		// 常见争议：EPS_G:4326 官方轴序是 lat,lon
		if SRid == "4326" {
			return true
		}
		// 对其他 SRid，可按需扩展
		return false
	default:
		return false
	}
}
func gisPoints(geometry []byte) (coords []float64, err error) {
	var event = "[gisPoints]"
	if err = json.Unmarshal(geometry, &coords); err != nil {
		err = ref.ErrAddPrintf(event, fmt.Errorf("解析几何数据失败: %v", err))
		return
	}
	return
}
func gisLineString(geometry []byte) (coords [][]float64, err error) {
	var event = "[gisLineString]"
	if err = json.Unmarshal(geometry, &coords); err != nil {
		err = ref.ErrAddPrintf(event, fmt.Errorf("解析几何数据失败: %v", err))
		return
	}
	return
}
func gisPolygon(geometry []byte) (polygon any, err error) {
	var event = "[gisPolygon]"
	if err = json.Unmarshal(geometry, &polygon); err != nil {
		err = ref.ErrAddPrintf(event, fmt.Errorf("解析几何数据失败: %v", err))
		return
	}
	return
}
func gisMultiPoints(geometry []byte) (res any, err error) {
	var multiPoints any
	var event = "[gisMultiPoints]"
	if err = json.Unmarshal(geometry, &multiPoints); err != nil {
		err = ref.ErrAddPrintf(event, fmt.Errorf("解析几何数据失败: %v", err))
		return
	}
	res = multiPoints
	return
}
func gisMultiLineString(geometry []byte) (res any, err error) {
	var multiLineString any
	var event = "[gisMultiLineString]"
	if err = json.Unmarshal(geometry, &multiLineString); err != nil {
		err = ref.ErrAddPrintf(event, fmt.Errorf("解析几何数据失败: %v", err))
		return
	}
	res = multiLineString
	return
}
func gisMultiPolygon(geometry []byte) (res any, err error) {
	var multiPolygon any
	var event = "[gisMultiPolygon]"
	if err = json.Unmarshal(geometry, &multiPolygon); err != nil {
		err = ref.ErrAddPrintf(event, fmt.Errorf("解析几何数据失败: %v", err))
		return
	}
	res = multiPolygon
	return
}
func wktCoordinatesType(cc Coordinates) (wktJson parDef.GisValWkt, err error) {
	var gisVal = new(parDef.GisValWkt)
	var event = "[wktCoordinatesType]"
	switch cc.Type {
	case "Point":
		gisVal.Type = "point"
		if gisVal.Coords, err = gisPoints(cc.Coordinates); err != nil {
			err = ref.ErrAddPrintf(event, err)
			return
		}
	case "LineString":
		gisVal.Type = "lineString"
		if gisVal.Coords, err = gisLineString(cc.Coordinates); err != nil {
			err = ref.ErrAddPrintf(event, err)
			return
		}
	case "Polygon":
		gisVal.Type = "polygon"
		if gisVal.Coords, err = gisPolygon(cc.Coordinates); err != nil {
			err = ref.ErrAddPrintf(event, err)
			return
		}
	case "MultiPoint":
		gisVal.Type = "multiPoint"
		if gisVal.Coords, err = gisMultiPoints(cc.Coordinates); err != nil {
			err = ref.ErrAddPrintf(event, err)
			return
		}
	case "MultiLineString":
		gisVal.Type = "multiLineString"
		if gisVal.Coords, err = gisMultiLineString(cc.Coordinates); err != nil {
			err = ref.ErrAddPrintf(event, err)
			return
		}
	case "MultiPolygon":
		gisVal.Type = "multiPolygon"
		if gisVal.Coords, err = gisMultiPolygon(cc.Coordinates); err != nil {
			err = ref.ErrAddPrintf(event, err)
			return
		}
	}
	wktJson = *gisVal
	return
}
func getLatitude(Type string) (res int) {
	switch Type {
	case "2001", "2002", "2003", "2004", "2005", "2006", "2007":
		return 2
	case "3001", "3002", "3003", "3004", "3005", "3006", "3007":
		return 3
	default:
		return 2
	}
}
func GeomWktToJson(s any) (res string, err error) {
	var gisVal = new(parDef.GisValWkt)
	var feature = new(Feature)
	var gisValByte []byte
	gisValComposition := newSplitGisValue(s.(string))
	gisVal.SRid, gisVal.Axis = gisValComposition.rid, gisValComposition.axis
	gisVal.Latitude = getLatitude(gisValComposition.gType)
	if feature.Geometry, err = gis.GeomMarshalJson(gisValComposition.gisValue); err != nil {
		err = ref.ErrAddPrintf("Oracle.GeomWktToJson", err)
		return
	}
	var g struct {
		Type        string          `json:"type"`
		Coordinates json.RawMessage `json:"coordinates"`
		Geometries  json.RawMessage `json:"geometries"`
	}
	if err = json.Unmarshal(feature.Geometry, &g); err != nil {
		err = ref.ErrAddPrintf("Oracle.GeomWktToJson.json.Unmarshal", err)
		return
	}
	switch g.Type {
	case "GeometryCollection":
		gisVal.Type = "geometryCollection"
		var gg []Coordinates
		if err = json.Unmarshal(g.Geometries, &gg); err != nil {
			err = ref.ErrAddPrintf("GeomWktToJson", err)
		}
		var coordsG []parDef.GisValWkt
		for _, v := range gg {
			var coordsSubG parDef.GisValWkt
			if coordsSubG, err = wktCoordinatesType(v); err != nil {
				err = ref.ErrAddPrintf("Oracle.GeomWktToJson", err)
				return
			}
			coordsSubG.Latitude = gisVal.Latitude
			coordsG = append(coordsG, coordsSubG)
		}
		gisVal.Coords = coordsG
	default:
		var gg = Coordinates{
			Type:        g.Type,
			Coordinates: g.Coordinates,
		}
		var ggR parDef.GisValWkt
		if ggR, err = wktCoordinatesType(gg); err != nil {
			err = ref.ErrAddPrintf("Oracle.GeomWktToJson", err)
			return
		}
		gisVal.Type = ggR.Type
		gisVal.Coords = ggR.Coords
	}
	if gisValByte, err = json.Marshal(gisVal); err != nil {
		err = ref.ErrAddPrintf("Oracle.GeomWktToJson.json.Marshal", err)
		return
	}
	res = string(gisValByte)
	return
}

func d2PointStringWkt(gisVal any, swap bool) (newWkt string) {
	switch gisVal.(type) {
	case []any:
		var subFloat []float64
		for _, subVal := range gisVal.([]any) {
			switch subVal.(type) {
			case float64:
				subFloat = append(subFloat, subVal.(float64))
			}
		}
		newWkt = fmt.Sprintf("%v %v", subFloat[0], subFloat[1])
		if swap {
			newWkt = fmt.Sprintf("%v %v", subFloat[1], subFloat[0])
		}
	}
	return
}

// MySQL不支持3d point
func d3PointStringWkt(_ any, _ bool) (newWkt string) {
	return
}
func d2LineStringWkt(gisVal any, swap bool) (newWkt string) {
	var lineStr []string
	for _, val := range getMultiCoords(gisVal) {
		var subFloat []float64
		for _, subVal := range val.([]any) {
			switch subVal.(type) {
			case float64:
				subFloat = append(subFloat, subVal.(float64))
			}
		}
		var linePoint = fmt.Sprintf("%v %v", subFloat[0], subFloat[1])
		if swap {
			linePoint = fmt.Sprintf("%v %v", subFloat[1], subFloat[0])
		}
		lineStr = append(lineStr, linePoint)
	}
	newWkt = fmt.Sprintf("(%v)", strings.Join(lineStr, ","))
	return
}
func d3LineStringWkt(_ any, _ bool) (newWkt string) {
	return
}
func pointStringWkt(gisVal parDef.GisValWkt, prefixSwitch bool) (newWkt []string, err error) {
	var event = "[pointStringWkt]"
	var res []string
	switch gisVal.Latitude {
	case 2:
		if pp := d2PointStringWkt(gisVal.Coords, shouldFlip(gisVal.Axis, gisVal.SRid)); len(pp) > 0 {
			res = []string{fmt.Sprintf("%v", pp)}
		}
	case 3:
		err = ref.ErrAddPrintf(event, fmt.Errorf("mysql Does not support 3d point"))
		if pp := d3PointStringWkt(gisVal.Coords, shouldFlip(gisVal.Axis, gisVal.SRid)); len(pp) > 0 {
			res = []string{fmt.Sprintf("%v", pp)}
		}
		return
	}
	if prefixSwitch {
		newWkt = append(newWkt, fmt.Sprintf("POINT(%v)", strings.Join(res, ",")))
	} else {
		newWkt = append(newWkt, fmt.Sprintf("(%v)", strings.Join(res, ",")))
	}
	return
}
func lineStringWkt(gisVal parDef.GisValWkt, prefixSwitch bool) (newWkt []string, err error) {
	var event = "[pointStringWkt]"
	var line []string
	switch gisVal.Latitude {
	case 2:
		var point []string
		for _, v := range getMultiCoords(gisVal.Coords) {
			point = append(point, d2PointStringWkt(v, shouldFlip(gisVal.Axis, gisVal.SRid)))
		}
		line = []string{strings.Join(point, ",")}
	case 3:
		var point []string
		err = ref.ErrAddPrintf(event, fmt.Errorf("mysql Does not support 3d lineString"))
		for _, v := range getMultiCoords(gisVal.Coords) {
			point = append(point, d3PointStringWkt(v, shouldFlip(gisVal.Axis, gisVal.SRid)))
		}
		line = []string{strings.Join(point, ",")}
		return
	}
	if prefixSwitch {
		newWkt = append(newWkt, fmt.Sprintf("LINESTRING(%v)", strings.Join(line, ",")))
	} else {
		newWkt = append(newWkt, fmt.Sprintf("(%v)", strings.Join(line, ",")))
	}
	return
}
func polygonStringWkt(gisVal parDef.GisValWkt, prefixSwitch bool) (newWkt []string, err error) {
	var event = "[polygonStringWkt]"
	for _, subPolygon := range getMultiCoords(gisVal.Coords) {
		var polygonStr []string
		var newPolygonStr string
		switch subPolygon.(type) {
		case []any:
			for _, subV := range subPolygon.([]any) {
				switch gisVal.Latitude {
				case 2:
					polygonStr = append(polygonStr, d2PointStringWkt(subV, shouldFlip(gisVal.Axis, gisVal.SRid)))
				case 3:
					err = ref.ErrAddPrintf(event, fmt.Errorf("mysql Does not support 3d polygon"))
					polygonStr = append(polygonStr, d3PointStringWkt(subV, shouldFlip(gisVal.Axis, gisVal.SRid)))
					return
				}
			}
		}
		newPolygonStr = fmt.Sprintf("%v", strings.Join(polygonStr, ","))
		newPolygonStr = fmt.Sprintf("(%v)", newPolygonStr)
		if prefixSwitch {
			newWkt = append(newWkt, fmt.Sprintf("POLYGON(%v)", newPolygonStr))
		} else {
			newWkt = append(newWkt, newPolygonStr)
		}
	}
	return
}
func getMultiCoords(coords any) (multiAnyCoords []any) {
	switch coords.(type) {
	case []any:
		multiAnyCoords = coords.([]any)
	case any:
		multiAnyCoords = append(multiAnyCoords, coords.(any))
	}
	return
}
func multiPointStringWkt(gisVal parDef.GisValWkt, prefixSwitch bool) (newWkt []string, err error) {
	var multiPoint []string
	var event = "[multiPointStringWkt]"
	for _, v := range getMultiCoords(gisVal.Coords) {
		var res []string
		switch v.(type) {
		case []any:
			switch gisVal.Latitude {
			case 2:
				res = append(res, d2PointStringWkt(v, shouldFlip(gisVal.Axis, gisVal.SRid)))
			case 3:
				err = ref.ErrAddPrintf(event, fmt.Errorf("mysql Does not support 3d multiPoint"))
				res = append(res, d3PointStringWkt(v, shouldFlip(gisVal.Axis, gisVal.SRid)))
				return
			}
		}
		multiPoint = append(multiPoint, strings.Join(res, ","))
	}
	if prefixSwitch {
		newWkt = append(newWkt, fmt.Sprintf("MULTIPOINT(%v)", strings.Join(multiPoint, ",")))
	} else {
		newWkt = append(newWkt, fmt.Sprintf("(%v)", strings.Join(multiPoint, ",")))
	}
	return
}
func multiLineStringWkt(gisVal parDef.GisValWkt, prefixSwitch bool) (newWkt []string, err error) {
	var multiLineString []string
	var event = "[multiLineStringWkt]"
	for _, v := range getMultiCoords(gisVal.Coords) {
		var res []string
		switch v.(type) {
		case []any:
			switch gisVal.Latitude {
			case 2:
				res = append(res, d2LineStringWkt(v, shouldFlip(gisVal.Axis, gisVal.SRid)))
			case 3:
				err = ref.ErrAddPrintf(event, fmt.Errorf("mysql Does not support 3d multiLineString"))
				res = append(res, d3LineStringWkt(v, shouldFlip(gisVal.Axis, gisVal.SRid)))
				return
			}
		}
		multiLineString = append(multiLineString, strings.Join(res, ","))
	}
	if prefixSwitch {
		newWkt = append(newWkt, fmt.Sprintf("MULTILINESTRING(%v)", strings.Join(multiLineString, ",")))
	} else {
		newWkt = append(newWkt, fmt.Sprintf("%v", strings.Join(multiLineString, ",")))
	}

	return
}
func multiPolygonWkt(gisVal parDef.GisValWkt, prefixSwitch bool) (multiPolygon []string, err error) {
	var event = "[multiPolygonWkt]"
	for _, v := range getMultiCoords(gisVal.Coords) {
		var subGisVal = gisVal
		var prefix string
		var res []string
		subGisVal.Type, subGisVal.Coords = "polygon", v
		if prefix, res, err = GeomJsonToWkt(subGisVal, prefixSwitch); err != nil {
			err = ref.ErrAddPrintf(event, err)
			return
		}
		if prefixSwitch {
			multiPolygon = append(multiPolygon, fmt.Sprintf("%v(%v)", prefix, strings.Join(res, ",")))
		} else {
			multiPolygon = append(multiPolygon, fmt.Sprintf("(%v)", strings.Join(res, ",")))
		}
	}
	return
}
func GeomJsonToWkt(gisVal parDef.GisValWkt, prefixSwitch bool) (prefix string, res []string, err error) {
	var event = "[GeomJsonToWkt]"
	switch gisVal.Type {
	case "point":
		res, err = pointStringWkt(gisVal, prefixSwitch)
		return "POINT", res, err
	case "lineString":
		res, err = lineStringWkt(gisVal, prefixSwitch)
		return "LINESTRING", res, err
	case "polygon":
		res, err = polygonStringWkt(gisVal, prefixSwitch)
		return "POLYGON", res, err
	case "multiPoint":
		res, err = multiPointStringWkt(gisVal, prefixSwitch)
		return "MULTIPOINT", res, err
	case "multiLineString":
		res, err = multiLineStringWkt(gisVal, prefixSwitch)
		return "MULTILINESTRING", res, err
	case "multiPolygon":
		res, err = multiPolygonWkt(gisVal, prefixSwitch)
		return "MULTIPOLYGON", res, err
	case "geometryCollection":
		var resGg []string
		for _, v := range getMultiCoords(gisVal.Coords) {
			var subGisVal parDef.GisValWkt
			var subGisValByte []byte
			if subGisValByte, err = json.Marshal(v); err != nil {
				err = ref.ErrAddPrintf(event, err)
				return
			}
			if err = json.Unmarshal(subGisValByte, &subGisVal); err != nil {
				err = ref.ErrAddPrintf(event, err)
				return
			}
			subGisVal.Axis = gisVal.Axis
			subGisVal.SRid = gisVal.SRid
			subGisVal.Latitude = gisVal.Latitude
			if prefix, res, err = GeomJsonToWkt(subGisVal, true); err != nil {
				err = ref.ErrAddPrintf(event, err)
				return
			}
			if prefixSwitch {
				resGg = append(resGg, fmt.Sprintf("%v(%v)", "GEOMETRYCOLLECTION", strings.Join(res, ",")))
			} else {
				resGg = append(resGg, fmt.Sprintf("%v", strings.Join(res, ",")))
			}
		}
		return "GEOMETRYCOLLECTION", resGg, err
	}
	return
}
